Skip to content
0

知识 - Encrypt

加解密基础

本内容将介绍接口数据加解密以及数据库字段值加解密。

加密主要利用了 hutool 提供的加密方法,其中有对称加密和非对称加密两种。

依赖:

xml
<dependencies>
   <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.32</version>
  </dependency>
  <dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-crypto</artifactId>
    <version>5.8.22</version>
  </dependency>
  <dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15to18</artifactId>
    <version>1.76</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>6.1.6</version>
  </dependency>
  <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.16</version>
  </dependency>
  <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
    <version>3.5.6</version>
    <optional>true</optional>
    <exclusions>
      <exclusion>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
      </exclusion>
    </exclusions>
  </dependency>
</dependencies>

枚举

加解密支持的算法枚举类

java
@Getter
@AllArgsConstructor
public enum AlgorithmType {
    /**
     * 默认走yml配置
     */
    DEFAULT(null),

    /**
     * base64
     */
    BASE64(Base64Encryptor.class),

    /**
     * aes
     */
    AES(AesEncryptor.class),

    /**
     * rsa
     */
    RSA(RsaEncryptor.class),

    /**
     * sm2
     */
    SM2(Sm2Encryptor.class),

    /**
     * sm4
     */
    SM4(Sm4Encryptor.class);

    private final Class<? extends AbstractEncryptor> clazz;

}

加解密算法

先提供 Encryptor 接口。

java
public interface Encryptor {
    /**
     * 获得当前算法
     */
    AlgorithmType algorithm();

    /**
     * 加密
     *
     * @param value      待加密字符串
     * @param encodeType 加密后的编码格式
     * @return 加密后的字符串
     */
    String encrypt(String value, EncodeType encodeType);

    /**
     * 解密
     *
     * @param value      待加密字符串
     * @return 解密后的字符串
     */
    String decrypt(String value);
}

基础实现类

java
public abstract class AbstractEncryptor implements Encryptor {

    public AbstractEncryptor(EncryptContext context) {
        // 用户配置校验与配置注入
    }
}

Base64 加解密类

java
public class Base64Encryptor extends AbstractEncryptor {

    public Base64Encryptor(EncryptContext context) {
        super(context);
    }

    @Override
    public AlgorithmType algorithm() {
        return AlgorithmType.BASE64;
    }

    @Override
    public String encrypt(String value, EncodeType encodeType) {
        return EncryptHelper.encryptByBase64(value);
    }

    @Override
    public String decrypt(String value) {
        return EncryptHelper.decryptByBase64(value);
    }
}

AES 算法实现

java
public class AesEncryptor extends AbstractEncryptor {

    private final EncryptContext context;

    public AesEncryptor(EncryptContext context) {
        super(context);
        this.context = context;
    }

    /**
     * 获得当前算法
     */
    @Override
    public AlgorithmType algorithm() {
        return AlgorithmType.AES;
    }

    /**
     * 加密
     *
     * @param value      待加密字符串
     * @param encodeType 加密后的编码格式
     */
    @Override
    public String encrypt(String value, EncodeType encodeType) {
        if (encodeType == EncodeType.HEX) {
            return EncryptHelper.encryptByAesHex(value, context.getPassword());
        } else {
            return EncryptHelper.encryptByAes(value, context.getPassword());
        }
    }

    /**
     * 解密
     *
     * @param value 待加密字符串
     */
    @Override
    public String decrypt(String value) {
        return EncryptHelper.decryptByAes(value, context.getPassword());
    }
}

RSA 算法

java
public class RsaEncryptor extends AbstractEncryptor {

    private final EncryptContext context;

    public RsaEncryptor(EncryptContext context) {
        super(context);
        String privateKey = context.getPrivateKey();
        String publicKey = context.getPublicKey();
        if (StringUtils.isAnyEmpty(privateKey, publicKey)) {
            throw new IllegalArgumentException("RSA公私钥均需要提供,公钥加密,私钥解密。");
        }
        this.context = context;
    }

    /**
     * 获得当前算法
     */
    @Override
    public AlgorithmType algorithm() {
        return AlgorithmType.RSA;
    }

    /**
     * 加密
     *
     * @param value      待加密字符串
     * @param encodeType 加密后的编码格式
     */
    @Override
    public String encrypt(String value, EncodeType encodeType) {
        if (encodeType == EncodeType.HEX) {
            return EncryptHelper.encryptByRsaHex(value, context.getPublicKey());
        } else {
            return EncryptHelper.encryptByRsa(value, context.getPublicKey());
        }
    }

    /**
     * 解密
     *
     * @param value 待加密字符串
     */
    @Override
    public String decrypt(String value) {
        return EncryptHelper.decryptByRsa(value, context.getPrivateKey());
    }
}

SM2 算法

java
public class Sm2Encryptor extends AbstractEncryptor {


    private final EncryptContext context;

    public Sm2Encryptor(EncryptContext context) {
        super(context);
        String privateKey = context.getPrivateKey();
        String publicKey = context.getPublicKey();
        if (StringUtils.isAnyEmpty(privateKey, publicKey)) {
            throw new IllegalArgumentException("SM2公私钥均需要提供,公钥加密,私钥解密。");
        }
        this.context = context;
    }

    /**
     * 获得当前算法
     */
    @Override
    public AlgorithmType algorithm() {
        return AlgorithmType.SM2;
    }

    /**
     * 加密
     *
     * @param value      待加密字符串
     * @param encodeType 加密后的编码格式
     */
    @Override
    public String encrypt(String value, EncodeType encodeType) {
        if (encodeType == EncodeType.HEX) {
            return EncryptHelper.encryptBySm2Hex(value, context.getPublicKey());
        } else {
            return EncryptHelper.encryptBySm2(value, context.getPublicKey());
        }
    }

    /**
     * 解密
     *
     * @param value 待加密字符串
     */
    @Override
    public String decrypt(String value) {
        return EncryptHelper.decryptBySm2(value, context.getPrivateKey());
    }
}

SM4 算法

java
public class Sm4Encryptor extends AbstractEncryptor {
    private final EncryptContext context;

    public Sm4Encryptor(EncryptContext context) {
        super(context);
        this.context = context;
    }

    /**
     * 获得当前算法
     */
    @Override
    public AlgorithmType algorithm() {
        return AlgorithmType.SM4;
    }

    /**
     * 加密
     *
     * @param value      待加密字符串
     * @param encodeType 加密后的编码格式
     */
    @Override
    public String encrypt(String value, EncodeType encodeType) {
        if (encodeType == EncodeType.HEX) {
            return EncryptHelper.encryptBySm4Hex(value, context.getPassword());
        } else {
            return EncryptHelper.encryptBySm4(value, context.getPassword());
        }
    }

    /**
     * 解密
     *
     * @param value 待加密字符串
     */
    @Override
    public String decrypt(String value) {
        return EncryptHelper.decryptBySm4(value, context.getPassword());
    }
}

加解密工具类

上面封装了很多的算法类,每个算法有自己的 API,那么封装了一个加解密工具类,对外提供 API 调用每个算法的 API。

java
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class EncryptHelper {
    /**
     * 公钥
     */
    public static final String PUBLIC_KEY = "publicKey";
    /**
     * 私钥
     */
    public static final String PRIVATE_KEY = "privateKey";

    /**
     * Base64 加密
     *
     * @param data 待加密数据
     * @return 加密后字符串
     */
    public static String encryptByBase64(String data) {
        return Base64.encode(data, StandardCharsets.UTF_8);
    }

    /**
     * Base64 解密
     *
     * @param data 待解密数据
     * @return 解密后字符串
     */
    public static String decryptByBase64(String data) {
        return Base64.decodeStr(data, StandardCharsets.UTF_8);
    }

    /**
     * AES 加密
     *
     * @param data     待解密数据
     * @param password 秘钥字符串
     * @return 加密后字符串, 采用Base64编码
     */
    public static String encryptByAes(String data, String password) {
        if (StrUtil.isBlank(password)) {
            throw new IllegalArgumentException("AES 需要传入秘钥信息");
        }
        // aes 算法的秘钥要求是 16 位、24 位、32 位
        int[] array = {16, 24, 32};
        if (!ArrayUtil.contains(array, password.length())) {
            throw new IllegalArgumentException("AES 秘钥长度要求为 16 位、24 位、32 位");
        }
        return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).encryptBase64(data, StandardCharsets.UTF_8);
    }

    /**
     * AES 加密
     *
     * @param data     待解密数据
     * @param password 秘钥字符串
     * @return 加密后字符串, 采用Hex编码
     */
    public static String encryptByAesHex(String data, String password) {
        if (StrUtil.isBlank(password)) {
            throw new IllegalArgumentException("AES 需要传入秘钥信息");
        }
        // aes 算法的秘钥要求是 16 位、24 位、32 位
        int[] array = {16, 24, 32};
        if (!ArrayUtil.contains(array, password.length())) {
            throw new IllegalArgumentException("AES 秘钥长度要求为 16 位、24 位、32 位");
        }
        return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).encryptHex(data, StandardCharsets.UTF_8);
    }

    /**
     * AES 解密
     *
     * @param data     待解密数据
     * @param password 秘钥字符串
     * @return 解密后字符串
     */
    public static String decryptByAes(String data, String password) {
        if (StrUtil.isBlank(password)) {
            throw new IllegalArgumentException("AES 需要传入秘钥信息");
        }
        // aes算法的秘钥要求是16位、24位、32位
        int[] array = {16, 24, 32};
        if (!ArrayUtil.contains(array, password.length())) {
            throw new IllegalArgumentException("AES 秘钥长度要求为 16 位、24 位、32 位");
        }
        return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).decryptStr(data, StandardCharsets.UTF_8);
    }

    /**
     * sm4 加密
     *
     * @param data     待加密数据
     * @param password 秘钥字符串
     * @return 加密后字符串, 采用Base64编码
     */
    public static String encryptBySm4(String data, String password) {
        if (StrUtil.isBlank(password)) {
            throw new IllegalArgumentException("SM4 需要传入秘钥信息");
        }
        // sm4 算法的秘钥要求是 16 位长度
        int sm4PasswordLength = 16;
        if (sm4PasswordLength != password.length()) {
            throw new IllegalArgumentException("SM4 秘钥长度要求为16位");
        }
        return SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8)).encryptBase64(data, StandardCharsets.UTF_8);
    }

    /**
     * sm4 加密
     *
     * @param data     待加密数据
     * @param password 秘钥字符串
     * @return 加密后字符串, 采用Base64编码
     */
    public static String encryptBySm4Hex(String data, String password) {
        if (StrUtil.isBlank(password)) {
            throw new IllegalArgumentException("SM4 需要传入秘钥信息");
        }
        // sm4 算法的秘钥要求是 16 位长度
        int sm4PasswordLength = 16;
        if (sm4PasswordLength != password.length()) {
            throw new IllegalArgumentException("SM4 秘钥长度要求为 16 位");
        }
        return SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8)).encryptHex(data, StandardCharsets.UTF_8);
    }

    /**
     * sm4 解密
     *
     * @param data     待解密数据
     * @param password 秘钥字符串
     * @return 解密后字符串
     */
    public static String decryptBySm4(String data, String password) {
        if (StrUtil.isBlank(password)) {
            throw new IllegalArgumentException("SM4 需要传入秘钥信息");
        }
        // sm4 算法的秘钥要求是 16 位长度
        int sm4PasswordLength = 16;
        if (sm4PasswordLength != password.length()) {
            throw new IllegalArgumentException("SM4 秘钥长度要求为 16 位");
        }
        return SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8)).decryptStr(data, StandardCharsets.UTF_8);
    }

    /**
     * 产生sm2加解密需要的公钥和私钥
     *
     * @return 公私钥Map
     */
    public static Map<String, String> generateSm2Key() {
        Map<String, String> keyMap = new HashMap<>(2);
        SM2 sm2 = SmUtil.sm2();
        keyMap.put(PRIVATE_KEY, sm2.getPrivateKeyBase64());
        keyMap.put(PUBLIC_KEY, sm2.getPublicKeyBase64());
        return keyMap;
    }

    /**
     * sm2 公钥加密
     *
     * @param data      待加密数据
     * @param publicKey 公钥
     * @return 加密后字符串, 采用Base64编码
     */
    public static String encryptBySm2(String data, String publicKey) {
        if (StrUtil.isBlank(publicKey)) {
            throw new IllegalArgumentException("SM2 需要传入公钥进行加密");
        }
        SM2 sm2 = SmUtil.sm2(null, publicKey);
        return sm2.encryptBase64(data, StandardCharsets.UTF_8, KeyType.PublicKey);
    }

    /**
     * sm2 公钥加密
     *
     * @param data      待加密数据
     * @param publicKey 公钥
     * @return 加密后字符串, 采用Hex编码
     */
    public static String encryptBySm2Hex(String data, String publicKey) {
        if (StrUtil.isBlank(publicKey)) {
            throw new IllegalArgumentException("SM2 需要传入公钥进行加密");
        }
        SM2 sm2 = SmUtil.sm2(null, publicKey);
        return sm2.encryptHex(data, StandardCharsets.UTF_8, KeyType.PublicKey);
    }

    /**
     * sm2 私钥解密
     *
     * @param data       待加密数据
     * @param privateKey 私钥
     * @return 解密后字符串
     */
    public static String decryptBySm2(String data, String privateKey) {
        if (StrUtil.isBlank(privateKey)) {
            throw new IllegalArgumentException("SM2 需要传入私钥进行解密");
        }
        SM2 sm2 = SmUtil.sm2(privateKey, null);
        return sm2.decryptStr(data, KeyType.PrivateKey, StandardCharsets.UTF_8);
    }

    /**
     * 产生 RSA 加解密需要的公钥和私钥
     *
     * @return 公私钥Map
     */
    public static Map<String, String> generateRsaKey() {
        Map<String, String> keyMap = new HashMap<>(2);
        RSA rsa = SecureUtil.rsa();
        keyMap.put(PRIVATE_KEY, rsa.getPrivateKeyBase64());
        keyMap.put(PUBLIC_KEY, rsa.getPublicKeyBase64());
        return keyMap;
    }

    /**
     * rsa 公钥加密
     *
     * @param data      待加密数据
     * @param publicKey 公钥
     * @return 加密后字符串, 采用 Base64 编码
     */
    public static String encryptByRsa(String data, String publicKey) {
        if (StrUtil.isBlank(publicKey)) {
            throw new IllegalArgumentException("RSA 需要传入公钥进行加密");
        }
        RSA rsa = SecureUtil.rsa(null, publicKey);
        return rsa.encryptBase64(data, StandardCharsets.UTF_8, KeyType.PublicKey);
    }

    /**
     * rsa 公钥加密
     *
     * @param data      待加密数据
     * @param publicKey 公钥
     * @return 加密后字符串, 采用 Hex 编码
     */
    public static String encryptByRsaHex(String data, String publicKey) {
        if (StrUtil.isBlank(publicKey)) {
            throw new IllegalArgumentException("RSA 需要传入公钥进行加密");
        }
        RSA rsa = SecureUtil.rsa(null, publicKey);
        return rsa.encryptHex(data, StandardCharsets.UTF_8, KeyType.PublicKey);
    }

    /**
     * rsa 私钥解密
     *
     * @param data       待加密数据
     * @param privateKey 私钥
     * @return 解密后字符串
     */
    public static String decryptByRsa(String data, String privateKey) {
        if (StrUtil.isBlank(privateKey)) {
            throw new IllegalArgumentException("RSA 需要传入私钥进行解密");
        }
        RSA rsa = SecureUtil.rsa(privateKey, null);
        return rsa.decryptStr(data, KeyType.PrivateKey, StandardCharsets.UTF_8);
    }

    /**
     * md5 加密
     *
     * @param data 待加密数据
     * @return 加密后字符串, 采用 Hex 编码
     */
    public static String encryptByMd5(String data) {
        return SecureUtil.md5(data);
    }

    /**
     * sha256 加密
     *
     * @param data 待加密数据
     * @return 加密后字符串, 采用 Hex 编码
     */
    public static String encryptBySha256(String data) {
        return SecureUtil.sha256(data);
    }

    /**
     * sm3 加密
     *
     * @param data 待加密数据
     * @return 加密后字符串, 采用 Hex 编码
     */
    public static String encryptBySm3(String data) {
        return SmUtil.sm3(data);
    }
}

接口数据加解密

一般重要的数据,在前后端交互的时候需要加解密:

  • 前端提交数据到后端前,先加密再提交,后端拿到数据后,要进行解密
  • 后端返回数据给前端前,先加密再提交,前端拿到数据后,要进行解密

加解密有很多算法方式,这里采用 Base64 算法、AES 算法、RSA 算法、

接口数据加解密实现步骤:

  1. 提供 ApiEncrypt 注解,在方法上使用后代表开启接口数据加密功能,里面有两个属性:request(对请求数据解密,非 GET 请求)、response(对响应的数据加密)
  2. 利用 Filter 过滤器来对有 ApiEncrypt 注解的请求数据进行解密,响应数据进行加密
  3. 加密解密采用 AES 算法,密钥是随机生成的 32 字符,该密钥 Base64 加密,然后又采用 RSA 公钥加密后传输。私钥在前端持有。解密密钥时,使用私钥解密。公钥为前端持有

注解

java
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiEncrypt {
    /**
     * 请求体数据是否进行解密,默认不解密,为 true 时解密
     */
    boolean request() default false;

    /**
     * 响应数据是否加密后返回,默认加密,为 true 时加密
     */
    boolean response() default true;
}

配置项

支持 Spring Boot 的 application 配置属性

java
@Data
@ConfigurationProperties(prefix = "api-decrypt")
public class ApiDecryptProperties {
    /**
     * 加密开关
     */
    private Boolean enabled;

    /**
     * 头部标识,告诉系统是否解密传过来的数据
     */
    private String headerFlag;

    /**
     * 响应加密公钥
     */
    private String publicKey;

    /**
     * 请求解密私钥
     */
    private String privateKey;
}

加解密缓存

项目初始化的时候,扫描有 ApiEncrypt 注解的属性,将其加解密算法等缓存起来,这样数据处理时,直接从缓存获取加解密信息。

java
@Data
public class EncryptContext {
    /**
     * 默认算法
     */
    private AlgorithmType algorithm;

    /**
     * 安全秘钥
     */
    private String password;

    /**
     * 公钥
     */
    private String publicKey;

    /**
     * 私钥
     */
    private String privateKey;

    /**
     * 编码方式,base64/hex
     */
    private EncodeType encode;
}

实现缓存的管理类,包括缓存 数据库字段值加密 的加解密信息

java
@Slf4j
@NoArgsConstructor
public class EncryptorManager {
    /**
     * 缓存加密器
     */
    Map<EncryptContext, Encryptor> encryptorMap = new ConcurrentHashMap<>();

    /**
     * 类加密字段缓存
     */
    Map<Class<?>, Set<Field>> fieldCache = new ConcurrentHashMap<>();

    /**
     * 构造方法传入类加密字段缓存
     *
     * @param typeAliasesPackage 实体类包
     */
    public EncryptorManager(String typeAliasesPackage) {
        scanEncryptClasses(typeAliasesPackage);
    }


    /**
     * 获取类加密字段缓存
     */
    public Set<Field> getFieldCache(Class<?> sourceClazz) {
        if (ObjectUtil.isNotNull(fieldCache)) {
            return fieldCache.get(sourceClazz);
        }
        return null;
    }

    /**
     * 注册加密执行者到缓存
     *
     * @param encryptContext 加密执行者需要的相关配置参数
     */
    public Encryptor registAndGetEncryptor(EncryptContext encryptContext) {
        if (encryptorMap.containsKey(encryptContext)) {
            return encryptorMap.get(encryptContext);
        }
        Encryptor encryptor = ReflectUtil.newInstance(encryptContext.getAlgorithm().getClazz(), encryptContext);
        encryptorMap.put(encryptContext, encryptor);
        return encryptor;
    }

    /**
     * 移除缓存中的加密执行者
     *
     * @param encryptContext 加密执行者需要的相关配置参数
     */
    public void removeEncryptor(EncryptContext encryptContext) {
        this.encryptorMap.remove(encryptContext);
    }

    /**
     * 根据配置进行加密。会进行本地缓存对应的算法和对应的秘钥信息。
     *
     * @param value          待加密的值
     * @param encryptContext 加密相关的配置信息
     */
    public String encrypt(String value, EncryptContext encryptContext) {
        Encryptor encryptor = this.registAndGetEncryptor(encryptContext);
        return encryptor.encrypt(value, encryptContext.getEncode());
    }

    /**
     * 根据配置进行解密
     *
     * @param value          待解密的值
     * @param encryptContext 加密相关的配置信息
     */
    public String decrypt(String value, EncryptContext encryptContext) {
        Encryptor encryptor = this.registAndGetEncryptor(encryptContext);
        return encryptor.decrypt(value);
    }

    /**
     * 通过 typeAliasesPackage 设置的扫描包 扫描缓存实体
     */
    private void scanEncryptClasses(String typeAliasesPackage) {
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
        String[] packagePatternArray = StringUtils.splitPreserveAllTokens(typeAliasesPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
        String classpath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX;
        try {
            for (String packagePattern : packagePatternArray) {
                String path = ClassUtils.convertClassNameToResourcePath(packagePattern);
                Resource[] resources = resolver.getResources(classpath + path + "/*.class");
                for (Resource resource : resources) {
                    ClassMetadata classMetadata = factory.getMetadataReader(resource).getClassMetadata();
                    Class<?> clazz = Resources.classForName(classMetadata.getClassName());
                    Set<Field> encryptFieldSet = getEncryptFieldSetFromClazz(clazz);
                    if (CollUtil.isNotEmpty(encryptFieldSet)) {
                        fieldCache.put(clazz, encryptFieldSet);
                    }
                }
            }
        } catch (Exception e) {
            log.error("初始化数据安全缓存时出错:{}", e.getMessage());
        }
    }

    /**
     * 获得一个类的加密字段集合
     */
    private Set<Field> getEncryptFieldSetFromClazz(Class<?> clazz) {
        Set<Field> fieldSet = new HashSet<>();
        // 判断clazz如果是接口,内部类,匿名类就直接返回
        if (clazz.isInterface() || clazz.isMemberClass() || clazz.isAnonymousClass()) {
            return fieldSet;
        }
        while (clazz != null) {
            Field[] fields = clazz.getDeclaredFields();
            fieldSet.addAll(Arrays.asList(fields));
            clazz = clazz.getSuperclass();
        }
        fieldSet = fieldSet.stream().filter(field ->
                        field.isAnnotationPresent(EncryptField.class) && field.getType() == String.class)
                .collect(Collectors.toSet());
        for (Field field : fieldSet) {
            field.setAccessible(true);
        }
        return fieldSet;
    }
}

过滤器

需要拦截 Request 和 Response 里的数据来进行处理,而 Spring MVC 只允许获取一次 Request 对象,如果不做如何处理,直接拦截到 Request 对象,那么项目里将无法二次使用 Request,因为 Request 的流被读取后,将无法二次读取,于是我们需要包装 Request 一层,读取流对象后,返回一个新的流对象,这个新的流对象就是原本的流对象(代理),这样将包装类返回给项目的时候,依然可以再次获取 Request 对象。Response 同理。

请求体包装类,实现解密功能

java
public class DecryptRequestBodyWrapper extends HttpServletRequestWrapper {
    private final byte[] body;

    /**
     * 采用 AES 加密数据,加密使用的密钥由 RSA 加密而得到
     *
     * @param request request
     * @param privateKey       RSA 私钥 (用于解密 AES 秘钥)
     * @param headerFlag      请求头标志
     */
    public DecryptRequestBodyWrapper(HttpServletRequest request, String privateKey, String headerFlag) throws IOException {
        super(request);
        // 获取 AES 密码,该密码采用 RSA 加密
        String headerRsa = request.getHeader(headerFlag);
        String decryptAes = EncryptHelper.decryptByRsa(headerRsa, privateKey);
        // 解密 AES 密码
        String aesPassword = EncryptHelper.decryptByBase64(decryptAes);
        request.setCharacterEncoding("UTF-8");
        byte[] readBytes = IoUtil.readBytes(request.getInputStream(), false);
        String requestBody = new String(readBytes, StandardCharsets.UTF_8);
        // 解密 body 采用 AES 加密
        String decryptBody = EncryptHelper.decryptByAes(requestBody, aesPassword);
        body = decryptBody.getBytes(StandardCharsets.UTF_8);
    }

    @Override
    public BufferedReader getReader() {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }


    @Override
    public int getContentLength() {
        return body.length;
    }

    @Override
    public long getContentLengthLong() {
        return body.length;
    }

    @Override
    public String getContentType() {
        return MediaType.APPLICATION_JSON_VALUE;
    }


    @Override
    public ServletInputStream getInputStream() {
        final ByteArrayInputStream bais = new ByteArrayInputStream(body);
        return new ServletInputStream() {
            @Override
            public int read() {
                return bais.read();
            }

            @Override
            public int available() {
                return body.length;
            }

            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }
        };
    }
}

响应体包装类,实现加密功能

java
public class EncryptResponseBodyWrapper extends HttpServletResponseWrapper {
    private final ByteArrayOutputStream byteArrayOutputStream;
    private final ServletOutputStream servletOutputStream;
    private final PrintWriter printWriter;

    public EncryptResponseBodyWrapper(HttpServletResponse response) throws IOException {
        super(response);
        this.byteArrayOutputStream = new ByteArrayOutputStream();
        this.servletOutputStream = this.getOutputStream();
        this.printWriter = new PrintWriter(new OutputStreamWriter(byteArrayOutputStream));
    }

    /**
     * 获取加密内容,加密采用 AES 加密,加密用的密钥由 RSA 公钥生成
     *
     * @param servletResponse response
     * @param publicKey       RSA 公钥 (用于加密 AES 秘钥)
     * @param headerFlag      请求头标志
     * @return 加密内容
     */
    public String getEncryptContent(HttpServletResponse servletResponse, String publicKey, String headerFlag) throws IOException {
        // 生成秘钥
        String aesPassword = RandomUtil.randomString(32);
        // 秘钥使用 Base64 编码
        String encryptAes = EncryptHelper.encryptByBase64(aesPassword);
        // Rsa 公钥加密 Base64 编码
        String encryptPassword = EncryptHelper.encryptByRsa(encryptAes, publicKey);

        // 设置响应头
        servletResponse.addHeader("Access-Control-Expose-Headers", headerFlag);
        servletResponse.setHeader(headerFlag, encryptPassword);
        servletResponse.setHeader("Access-Control-Allow-Origin", "*");
        servletResponse.setHeader("Access-Control-Allow-Methods", "*");
        servletResponse.setCharacterEncoding(StandardCharsets.UTF_8.toString());

        // 获取原始内容
        String originalBody = this.getContent();
        // 对内容进行加密
        return EncryptHelper.encryptByAes(originalBody, aesPassword);
    }

    @Override
    public PrintWriter getWriter() {
        return printWriter;
    }

    @Override
    public void flushBuffer() throws IOException {
        if (servletOutputStream != null) {
            servletOutputStream.flush();
        }
        if (printWriter != null) {
            printWriter.flush();
        }
    }

    @Override
    public void reset() {
        byteArrayOutputStream.reset();
    }

    public byte[] getResponseData() throws IOException {
        flushBuffer();
        return byteArrayOutputStream.toByteArray();
    }

    public String getContent() throws IOException {
        flushBuffer();
        return byteArrayOutputStream.toString();
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        return new ServletOutputStream() {
            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setWriteListener(WriteListener writeListener) {

            }

            @Override
            public void write(int b) throws IOException {
                byteArrayOutputStream.write(b);
            }

            @Override
            public void write(byte[] b) throws IOException {
                byteArrayOutputStream.write(b);
            }

            @Override
            public void write(byte[] b, int off, int len) throws IOException {
                byteArrayOutputStream.write(b, off, len);
            }
        };
    }
}

过滤器实现类

是西安 Servlet 提供的 Filter 接口来是实现拦截功能。

java
public class CryptoFilter implements Filter {
    private final ApiDecryptProperties properties;

    public CryptoFilter(ApiDecryptProperties properties) {
        this.properties = properties;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest servletRequest = (HttpServletRequest) request;
        HttpServletResponse servletResponse = (HttpServletResponse) response;
        // 获取加密注解
        ApiEncrypt apiEncrypt = getApiEncryptAnnotation(servletRequest);
        boolean requestFlag = Objects.nonNull(apiEncrypt) && apiEncrypt.request();
        boolean responseFlag = Objects.nonNull(apiEncrypt) && apiEncrypt.response();
        ServletRequest requestWrapper = null;
        ServletResponse responseWrapper = null;
        EncryptResponseBodyWrapper responseBodyWrapper = null;

        // 是否执行解密 && 是否为 put 或者 post 请求
        if (requestFlag && (HttpMethod.PUT.matches(servletRequest.getMethod()) || HttpMethod.POST.matches(servletRequest.getMethod()))) {
            // 是否存在解密标头
            String headerValue = servletRequest.getHeader(properties.getHeaderFlag());
            if (StringUtil.hasText(headerValue)) {
                // 请求解密
                requestWrapper = new DecryptRequestBodyWrapper(servletRequest, properties.getPrivateKey(), properties.getHeaderFlag());
            } else if (requestFlag) {
                // 是否有注解,有就报错,没有放行
                HandlerExceptionResolver exceptionResolver = SpringHelper.getBean("handlerExceptionResolver", HandlerExceptionResolver.class);
                exceptionResolver.resolveException(
                        servletRequest, servletResponse, null,
                        new ServiceException(ResponseStatusEnum.REQ_REJECT.getCode(), ResponseStatusEnum.REQ_REJECT.getStatus(), "没有访问权限,请联系管理员授权"));
                return;
            }
        }

        // 判断是否响应加密
        if (responseFlag) {
            responseBodyWrapper = new EncryptResponseBodyWrapper(servletResponse);
            responseWrapper = responseBodyWrapper;
        }

        chain.doFilter(
                ObjectUtil.defaultIfNull(requestWrapper, request),
                ObjectUtil.defaultIfNull(responseWrapper, response));

        if (responseFlag) {
            servletResponse.reset();
            // 对原始内容加密
            String encryptContent = responseBodyWrapper.getEncryptContent(
                    servletResponse, properties.getPublicKey(), properties.getHeaderFlag());
            // 对加密后的内容写出
            servletResponse.getWriter().write(encryptContent);
        }
    }

    /**
     * 获取 ApiEncrypt 注解
     */
    private ApiEncrypt getApiEncryptAnnotation(HttpServletRequest servletRequest) {
        RequestMappingHandlerMapping handlerMapping = SpringHelper.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class);
        // 获取注解
        try {
            HandlerExecutionChain mappingHandler = handlerMapping.getHandler(servletRequest);
            if (Objects.nonNull(mappingHandler)) {
                Object handler = mappingHandler.getHandler();
                // 从 handler 获取注解
                if (handler instanceof HandlerMethod handlerMethod) {
                    return handlerMethod.getMethodAnnotation(ApiEncrypt.class);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return null;
    }

    @Override
    public void destroy() {
    }
}

容器装配

最后我们将实现的过滤器功能注入到 Spring 的容器里。

java
@AutoConfiguration
@EnableConfigurationProperties(ApiDecryptProperties.class)
@ConditionalOnProperty(value = "api-decrypt.enabled", havingValue = "true")
public class ApiDecryptAutoConfiguration {
    @Bean
    public FilterRegistrationBean<CryptoFilter> cryptoFilterRegistration(ApiDecryptProperties properties) {
        FilterRegistrationBean<CryptoFilter> registration = new FilterRegistrationBean<>();
        registration.setDispatcherTypes(DispatcherType.REQUEST);
        registration.setFilter(new CryptoFilter(properties));
        registration.addUrlPatterns("/*");
        registration.setName("cryptoFilter");
        registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
        return registration;
    }
}

数据库字段值加解密

  1. 实现 EncryptField 注解,在字段使用后代表开启字段值加解密功能,具体加密规则在 EncryptField 里传入
  2. 实现 Mybatis insert、update 拦截器和 select 拦截器,前者对有 EncryptField 注解的字段值进行加密,后者对有 EncryptField 注解的字段值进行解密
  3. 内置缓存功能,在项目启动的时候,扫描实体类,将带有 EncryptField 注解的字段在 EncryptorManager 类里进行缓存,在第 2 步的拦截器中,进行获取并处理加解密

注意事项:第 3 点说明了需要扫描实体类并缓存,因此使用该功能,需要在 application 配置文件指定有 EncryptField 的实体类的包名。

注解

数据库数据加解密注解

java
@Documented
@Inherited
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptField {
    /**
     * 加密算法
     */
    AlgorithmType algorithm() default AlgorithmType.DEFAULT;

    /**
     * 秘钥。AES、SM4 需要
     */
    String password() default "";

    /**
     * 公钥。RSA、SM2 需要
     */
    String publicKey() default "";

    /**
     * 私钥。RSA、SM2 需要
     */
    String privateKey() default "";

    /**
     * 编码方式。对加密算法为 base64 不起作用
     */
    EncodeType encode() default EncodeType.DEFAULT;
}

枚举

编码类型枚举类

java
public enum EncodeType {
    /**
     * 默认使用 yml 配置
     */
    DEFAULT,

    /**
     * base64 编码
     */
    BASE64,

    /**
     * 16 进制编码
     */
    HEX;
}

配置项

支持 Spring Boot 的 application 配置属性

java
@Data
@ConfigurationProperties(prefix = "mybatis-encryptor")
public class EncryptorProperties {
    /**
     * 过滤开关
     */
    private Boolean enable;

    /**
     * 默认算法
     */
    private AlgorithmType algorithm;

    /**
     * 安全秘钥
     */
    private String password;

    /**
     * 公钥
     */
    private String publicKey;

    /**
     * 私钥
     */
    private String privateKey;

    /**
     * 编码方式,base64/hex
     */
    private EncodeType encode;

    /**
     * 加密实体类包路径
     */
    private String classPackage;
}

Mybatis 拦截器

Mybatis 提供了进和出数据库的拦截器 Interceptor 接口,需要自定义类实现它,这样 Mybatis 会在数据进出数据库时候调用。

MybatisDecryptInterceptor 类需要对 Insert、Update 到数据库的数据进行拦截,然后加密

java
@Slf4j
@Intercepts({@Signature(
        type = ResultSetHandler.class,
        method = "handleResultSets",
        args = {Statement.class})
})
@AllArgsConstructor
public class MybatisDecryptInterceptor implements Interceptor {

    private final EncryptorManager encryptorManager;
    private final EncryptorProperties defaultProperties;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获取执行 mysql 执行结果
        Object result = invocation.proceed();
        if (result == null) {
            return null;
        }
        decryptHandler(result);
        return result;
    }

    /**
     * 解密对象
     *
     * @param sourceObject 待加密对象
     */
    private void decryptHandler(Object sourceObject) {
        if (Objects.isNull(sourceObject)) {
            return;
        }
        if (sourceObject instanceof Map<?, ?> map) {
            new HashSet<>(map.values()).forEach(this::decryptHandler);
            return;
        }
        if (sourceObject instanceof List<?> list) {
            if(CollUtil.isEmpty(list)) {
                return;
            }
            // 判断第一个元素是否含有注解。如果没有直接返回,提高效率
            Object firstItem = list.get(0);
            if (Objects.isNull(firstItem) || CollUtil.isEmpty(encryptorManager.getFieldCache(firstItem.getClass()))) {
                return;
            }
            list.forEach(this::decryptHandler);
            return;
        }
        // 不在缓存中的类,就是没有加密注解的类(当然也有可能是typeAliasesPackage写错)
        Set<Field> fields = encryptorManager.getFieldCache(sourceObject.getClass());
        if(Objects.isNull(fields)){
            return;
        }
        try {
            for (Field field : fields) {
                field.set(sourceObject, this.decryptField(Convert.toStr(field.get(sourceObject)), field));
            }
        } catch (Exception e) {
            log.error("处理解密字段时出错", e);
        }
    }

    /**
     * 字段值进行加密。通过字段的批注注册新的加密算法
     *
     * @param value 待加密的值
     * @param field 待加密字段
     * @return 加密后结果
     */
    private String decryptField(String value, Field field) {
        if (Objects.isNull(value)) {
            return null;
        }
        EncryptField encryptField = field.getAnnotation(EncryptField.class);
        EncryptContext encryptContext = new EncryptContext();
        encryptContext.setAlgorithm(encryptField.algorithm() == AlgorithmType.DEFAULT ? defaultProperties.getAlgorithm() : encryptField.algorithm());
        encryptContext.setEncode(encryptField.encode() == EncodeType.DEFAULT ? defaultProperties.getEncode() : encryptField.encode());
        encryptContext.setPassword(!StringUtil.hasText(encryptField.password()) ? defaultProperties.getPassword() : encryptField.password());
        encryptContext.setPrivateKey(!StringUtil.hasText(encryptField.privateKey()) ? defaultProperties.getPrivateKey() : encryptField.privateKey());
        encryptContext.setPublicKey(!StringUtil.hasText(encryptField.publicKey()) ? defaultProperties.getPublicKey() : encryptField.publicKey());
        return this.encryptorManager.decrypt(value, encryptContext);
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

MybatisEncryptInterceptor 类需要对 Select 出数据库的数据进行拦截,然后解密

java
@Slf4j
@Intercepts({@Signature(
        type = ParameterHandler.class,
        method = "setParameters",
        args = {PreparedStatement.class})
})
@AllArgsConstructor
public class MybatisEncryptInterceptor implements Interceptor {

    private final EncryptorManager encryptorManager;

    private final EncryptorProperties defaultProperties;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        return invocation;
    }

    @Override
    public Object plugin(Object target) {
        if (target instanceof ParameterHandler parameterHandler) {
            // 进行加密操作
            Object parameterObject = parameterHandler.getParameterObject();
            if (Objects.nonNull(parameterObject) && !(parameterObject instanceof String)) {
                encryptHandler(parameterObject);
            }
        }
        return target;
    }

    /**
     * 加密对象
     *
     * @param sourceObject 待加密对象
     */
    private void encryptHandler(Object sourceObject) {
        if (Objects.isNull(sourceObject)) {
            return;
        }
        if (sourceObject instanceof Map<?, ?> map) {
            new HashSet<>(map.values()).forEach(this::encryptHandler);
            return;
        }
        if (sourceObject instanceof List<?> list) {
            if(CollUtil.isEmpty(list)) {
                return;
            }
            // 判断第一个元素是否含有注解。如果没有直接返回,提高效率
            Object firstItem = list.get(0);
            if (Objects.isNull(firstItem) || CollUtil.isEmpty(encryptorManager.getFieldCache(firstItem.getClass()))) {
                return;
            }
            list.forEach(this::encryptHandler);
            return;
        }
        // 不在缓存中的类,就是没有加密注解的类(当然也有可能是 typeAliasesPackage 写错)
        Set<Field> fields = encryptorManager.getFieldCache(sourceObject.getClass());
        if(Objects.isNull(fields)){
            return;
        }
        try {
            for (Field field : fields) {
                field.set(sourceObject, encryptField(Convert.toStr(field.get(sourceObject)), field));
            }
        } catch (Exception e) {
            log.error("处理加密字段时出错", e);
        }
    }

    /**
     * 字段值进行加密。通过字段的批注注册新的加密算法
     *
     * @param value 待加密的值
     * @param field 待加密字段
     * @return 加密后结果
     */
    private String encryptField(String value, Field field) {
        if (Objects.isNull(value)) {
            return null;
        }
        EncryptField encryptField = field.getAnnotation(EncryptField.class);
        EncryptContext encryptContext = new EncryptContext();
        encryptContext.setAlgorithm(encryptField.algorithm() == AlgorithmType.DEFAULT ? defaultProperties.getAlgorithm() : encryptField.algorithm());
        encryptContext.setEncode(encryptField.encode() == EncodeType.DEFAULT ? defaultProperties.getEncode() : encryptField.encode());
        encryptContext.setPassword(!StringUtil.hasText(encryptField.password()) ? defaultProperties.getPassword() : encryptField.password());
        encryptContext.setPrivateKey(!StringUtil.hasText(encryptField.privateKey()) ? defaultProperties.getPrivateKey() : encryptField.privateKey());
        encryptContext.setPublicKey(!StringUtil.hasText(encryptField.publicKey()) ? defaultProperties.getPublicKey() : encryptField.publicKey());
        return this.encryptorManager.encrypt(value, encryptContext);
    }


    @Override
    public void setProperties(Properties properties) {
    }
}

容器装配

将实现了 Mybatis 的拦截器注入到 Spring 容器里。

java
@AutoConfiguration(after = MybatisPlusAutoConfiguration.class)
@EnableConfigurationProperties(EncryptorProperties.class)
@ConditionalOnProperty(value = "mybatis-encryptor.enable", havingValue = "true")
@Slf4j
@RequiredArgsConstructor
public class EncryptorAutoConfiguration {

    private final EncryptorProperties properties;

    @Bean
    public EncryptorManager encryptorManager() {
        return new EncryptorManager(properties.getClassPackage());
    }

    @Bean
    public MybatisEncryptInterceptor mybatisEncryptInterceptor(EncryptorManager encryptorManager) {
        return new MybatisEncryptInterceptor(encryptorManager, properties);
    }

    @Bean
    public MybatisDecryptInterceptor mybatisDecryptInterceptor(EncryptorManager encryptorManager) {
        return new MybatisDecryptInterceptor(encryptorManager, properties);
    }
}

扫描容器装配类

Spring Boot 3.x 需要在 resource 下建立 META-INF/spring 路径,然后创建 org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件,内容为

java
cn.youngkbt.encrypt.config.EncryptorAutoConfiguration
cn.youngkbt.encrypt.config.ApiDecryptAutoConfiguration

这样 Spring 会自动扫描该文件的两个容器装配类,将里面涉及的类注入到 Spring 容器。

使用案例

接口数据加解密使用:

java
@Slf4j
@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
@Validated
public class AuthController {

  @PostMapping("/login")
  @ApiEncrypt(request = true, response = false)
  public String login(@Valid @RequestBody LoginUserDTO loginUserDTO) {
    return "执行登录";
  }
}

使用了 ApiEncrypt 注解,代表前端发生的数据需要进行解密,后端返回的数据不需要加密。

数据库字段值加解密使用:

java
@Data
@TableName("test_encrypt")
public class DemoEncryptPO {

    @TableId
    private Long id;

    @EncryptField(algorithm = AlgorithmType.RSA,
            privateKey = """
                    MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCcHvPEBJOit8uM
                    OJcL/6LwxBGrslfVU6iL/ka7W8Ux5ifr/lJUe+e0atMFb9hYL5VbfbexdI189uEu
                    UCT9U9aDgPW8A8SWg7DR0mXT/+tEnnl/RdsaeFke4HTDwOZJaBP4ljE0+pJnH5Xj
                    qxT803qo5EtXOdjv6fyysutpsAYLPu65nwWnKIGLyFvdvHrNAsM6i2TSWqpU6AAK
                    6EN3Jht83T1PvJfBL7AanjhUGMSbCAzl6w5sh9IJw0SnOA1Nk1/nRqWwJSJYf8Ri
                    3WvuRPUq8rjwU1tpvFNndlNtrtZeheCxBTAqpdq3RIHMkYHb/A7rfhinEqAJ+J6m
                    atYJ/tdFAgMBAAECggEAf6iboVwwsKxjh6w6brhBL2jUHjZ9tdVri9WHVurKi2vs
                    lv9lqOmMZK25EcgL+sgl9CMPks6AZ3+kT+9+35qvXOaViYD3PjA+5MOLOlgYtAp1
                    xMmFSNbB4Qqez/arF7lAI1QEtpJyJlrggMLDLZ1rvjC3HoMRzdPiKuk8sDYcKxKq
                    j1JIEULy3DYdPvrFI2FmcOsMtikP2bbKY4USStgA8r5KF7CtjJjjyjrkYwc61IIJ
                    haL+pFQf0Im+BEjPWoNXUK3LHNTIq8S8BirzibZmTfZCfnZzi7olvzJRc+l3XTz3
                    SgQb9Y/A3KOlrXd7bse+OzkuSDq9izR2x4ejayWO/QKBgQDPRTF7BBhrS6aq4Rkm
                    crtbYyHZ68kfyROZ3EIi+I/7nBPqnDaFYceEAIN2P6/25PPLBiEi5fI5DMnvzIT1
                    2PoFGSKTh6Y34VL6oFn3Fd5uHdmpLguaHu/lSN1M+Gry9E/mqh2zijZJLCKKWgY8
                    LX66eNnVdMftSMHFOSSli0KmHwKBgQDA00XwAls5r6/ClwxR7hDEGZ2iQ7L8UZWP
                    iT1aCPkKRvjjsO0TDyxmO+KJy9tMB1B49xpyPkApzErqueLyEEWYB0i3kSrTtGzM
                    J1MjMSPkRkgoJH4x4MW03eHPkDVa5q+oDBhVMX0yEQXWZ1iC3saRSgPlybE68Mja
                    FgwP2qpuGwKBgFPZ6T+cE4jsrPt6XyNXzQYWn646nj4WqbBYFAVzy0P+C2yhT8k8
                    GmwDjSt8bmKSkzIyQ5uLrSd5TgSOF8ghxFvlpEBM42i95kTwNBUqqrafqtuvfhAW
                    rfRzOtwVr6akQeLONX/ZzUZi7YJNEzKrMRadJ3scaHlNMt7n1DSIlyj/AoGABhEf
                    rDOGx0Pd1dOG0bUZ1fGwYgCbSxEOEZwR0BlkLIybHB7e9rCNhxHvSMKfPb8lKwkr
                    Tdjjj+0bllMO7urQJb5k7VGl9U9B9RJvrTXImVAUyR6M0ejuj4hDqJIy+48yi6kF
                    wvhxpfefJWXPBR8ZREz93mcAKoiU6Te0XXNV1W0CgYBVIsQGigYQTDiUpkL0mBHH
                    +10kMc/NWgOYPKJ5R8sOIC0YdDem368Udg6g0naOpyIQX9HR7knERtw9/fwWWdcs
                    mCP8Xuw4gHoOjjlVWPGM4Gx5f7+MPxxdwUrTf0jnnuNWuSysQ+cfGkbuyot/8Ar7
                    zE90AMVK2EgAPpuYtxv9gA==
                    """,
            publicKey = """
                    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnB7zxASTorfLjDiXC/+i
                    8MQRq7JX1VOoi/5Gu1vFMeYn6/5SVHvntGrTBW/YWC+VW323sXSNfPbhLlAk/VPW
                    g4D1vAPEloOw0dJl0//rRJ55f0XbGnhZHuB0w8DmSWgT+JYxNPqSZx+V46sU/NN6
                    qORLVznY7+n8srLrabAGCz7uuZ8FpyiBi8hb3bx6zQLDOotk0lqqVOgACuhDdyYb
                    fN09T7yXwS+wGp44VBjEmwgM5esObIfSCcNEpzgNTZNf50alsCUiWH/EYt1r7kT1
                    KvK48FNbabxTZ3ZTba7WXoXgsQUwKqXat0SBzJGB2/wO634YpxKgCfiepmrWCf7X
                    RQIDAQAB
                    """
    )
    private String testKey;

    @EncryptField(algorithm = AlgorithmType.AES, password = "10rfylhtccpuyke5")
    private String value;
}

使用 EncryptField 注解,代表 value 在存入数据库的时候采用 AES 加密,读取时采用 AES 解密。其中 password 为密钥,privateKey 为私钥,publicKey wei 公钥

最近更新